import * as path from "path";
import * as fs from "fs";
import { Injectable } from "@nestjs/common";
import { JSDOM } from "jsdom";
import { PrismaClient } from "@prisma/client";
import configuration from "src/common/configuration";
import { marked } from "marked";
import DOMPurify from "dompurify";
import { Sitdown } from "sitdown";
import {
  ArticleContentDataVersion,
  ArticleContentDataType,
} from "./enum/article-content-data";
import {
  ArticleContentData,
  ArticleContentNode,
  ArticleContentNodeV1,
} from "./interface/article-content-data";
import { ArticlePublishDTO } from "./dto/article-publish.dto";
import { ArticleUpdateDTO } from "./dto/article-update.dto";
import {
  ArticleDTODealError,
  DealtArticleDTO,
} from "./interface/article-dealt";
import { validateXML } from "xsd-schema-validator";
const sitdown = new Sitdown({
  codeBlockStyle: "fenced",
});

const articleDirectoryPath = configuration.resource.articleDirectoryPath;
const articleContentXSDPath = path.join(
  __dirname,
  "../../public/schema/article-content.xsd",
);
// const articleContentXSD = fs.readFileSync(articleContentXSDPath, "utf-8");

// 加载中、加载失败图片路径
// const imgLoadingSrc = "/images/image.loading.png";
const imgErrorSrc = "/images/image.error.png";

@Injectable()
export class ArticleService {
  constructor(private readonly prisma: PrismaClient) {}

  /**
   * 将Markdown格式的文本转换为HTML格式。
   * @param markdown 要转换的Markdown文本。
   * @param purify 是否对生成的HTML进行净化，默认为false。如果设置为true，则会使用DOMPurify进行HTML的净化，以防止XSS攻击。
   * @returns 返回转换后的HTML文本。如果purify为true，则返回净化后的HTML文本。
   */
  async markdown2HTML(
    markdown: string,
    purify: boolean = false,
  ): Promise<string> {
    const html = await marked.parse(markdown);
    if (!purify) {
      return html;
    }
    const window = new JSDOM().window;
    try {
      const purifyHTML = DOMPurify(window).sanitize(html);
      return purifyHTML;
    } catch (e) {
      console.error(e);
      return html;
    }
  }
  HTML2Markdown(html: string) {
    return sitdown.HTMLToMD(html);
  }

  /**
   * 获取文章内容的文件路径。
   * @param publisherID 发布者的ID，可以是数字或字符串。
   * @param articleID 文章的ID，可以是数字或字符串。
   * @param contentType 文章内容的类型，只能是"json"或"markdown"。
   * @returns 返回文章内容文件的完整路径，如果contentType不是"json"或"markdown"，则返回null。
   */
  getArticleContentDataFilePath(
    publisherID: number | string,
    articleID: number | string,
  ) {
    return `${articleDirectoryPath}/${publisherID}/${articleID}.json`;
  }
  getArticleContentMarkdownFilePath(
    publisherID: number | string,
    articleID: number | string,
  ) {
    return `${articleDirectoryPath}/${publisherID}/${articleID}.md`;
  }

  /**
   * 获取指定文章的ContentData。
   *
   * @param article 包含文章基本信息的对象，必须包括id, publisher_id, sort_id, title四个属性。
   * @returns 返回文章内容的JSON对象，如果找不到文章或读取失败则返回null。
   */
  getArticleContentData(
    article: {
      id: number;
      publisher_id: number;
      sort_id: number;
      title: string;
    },
    contentLength?: number,
  ): ArticleContentData {
    let text = null;
    try {
      // json 路径
      let filePath = this.getArticleContentDataFilePath(
        article.publisher_id,
        article.id,
      );
      if (!fs.existsSync(filePath)) {
        // 兼容旧文件
        filePath = `${articleDirectoryPath}/${article.publisher_id}/${article.sort_id}/${article.id}.json`;
      }
      if (!fs.existsSync(filePath)) {
        // 兼容旧文件
        filePath = `${articleDirectoryPath}/${article.publisher_id}/${article.sort_id}/${article.id}/${article.title}.json`;
      }
      if (!fs.existsSync(filePath)) {
        // 兼容旧的 markdown v1 版本
        filePath = this.getArticleContentMarkdownFilePath(
          article.publisher_id,
          article.id,
        );
      }
      if (!fs.existsSync(filePath)) {
        return null;
      }

      text = fs.readFileSync(filePath, "utf-8");
      if (!text) return null;
      const data: ArticleContentData = JSON.parse(text);
      // 数组，json v1版本
      if (Array.isArray(data)) {
        let nodes: ArticleContentNode[] = data;
        if (contentLength) {
          nodes = nodes.slice(0, contentLength / 10);
        }
        return {
          type: ArticleContentDataType.JSON,
          version: ArticleContentDataVersion.V1,
          content: nodes, // data is ArticleContentNode[]
        };
      }
      // 是对象，json v2 版本
      // 有 markdown 文件
      if (!data.content) {
        const parsedPath = path.parse(filePath);
        const markdownFilePath = parsedPath.dir + "/" + parsedPath.name + ".md";
        data.content = fs.readFileSync(markdownFilePath, "utf-8");
        if (contentLength) {
          data.content = data.content.substring(0, contentLength);
        }
      }
      return data;
    } catch (e) {
      console.error(e);
      // 读取到是 markdown，JSON.parse 错误
      // 旧的 markdown 存储方式（md文件）
      if (e instanceof SyntaxError) {
        if (contentLength) {
          text = text.substring(0, contentLength);
        }
        return {
          type: ArticleContentDataType.Markdown,
          version: ArticleContentDataVersion.V1,
          content: text,
        };
      }
      return null;
    }
  }
  /**
   * 删除指定文章的内容数据文件。
   * @param article 包含文章ID和发布者ID的对象。
   * 不返回任何内容。
   */
  deleteArticleContentDataFile(publisherID: number, articleID: number) {
    try {
      const dataPath = this.getArticleContentDataFilePath(
        publisherID,
        articleID,
      );
      const markdownPath = this.getArticleContentMarkdownFilePath(
        publisherID,
        articleID,
      );
      if (fs.existsSync(dataPath)) fs.rmSync(dataPath);
      if (fs.existsSync(markdownPath)) fs.rmSync(markdownPath);
    } catch (e) {
      console.error(e);
    }
  }
  /**
   * 通过 ContentElement 保存文章内容数据
   * @param article 包含文章id和发布者id的对象
   * @param contentElement 文章内容的HTML元素
   * 该函数不返回任何内容，但可能抛异常
   */
  saveArticleContentDataByElement(
    article: { id: number; publisher_id: number },
    contentElement: Element,
  ): void | never {
    const filePath = this.getArticleContentDataFilePath(
      article.publisher_id,
      article.id,
    );
    const dirPath = path.dirname(filePath);
    if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: true });

    const contentArray = this.articleContentXMLElement2Array(contentElement);
    const data: ArticleContentData = {
      type: ArticleContentDataType.JSON,
      version: ArticleContentDataVersion.V2,
      content: contentArray,
    };
    fs.writeFileSync(filePath, JSON.stringify(data), "utf-8");
  }
  /**
   * 通过 xml 字符串保存文章内容数据
   * @param article 包含文章ID和发布者ID的对象
   * @param xmlString 文章内容的XML字符串表示
   * 该函数不返回任何内容，但可能抛异常
   */
  saveArticleContentDataByXML(
    article: { id: number; publisher_id: number },
    xmlString: string,
  ): void | never {
    const filePath = this.getArticleContentDataFilePath(
      article.publisher_id,
      article.id,
    );
    const dirPath = path.dirname(filePath);
    if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: true });

    const contentArray = this.articleContentXMLString2Array(xmlString);
    const data: ArticleContentData = {
      type: ArticleContentDataType.JSON,
      version: ArticleContentDataVersion.V2,
      content: contentArray,
    };
    fs.writeFileSync(filePath, JSON.stringify(data), "utf-8");
  }
  /**
   * 通过 Markdown 格式保存文章内容数据
   * @param article 文章对象，包含文章id和发布者id
   * @param markdown Markdown格式的文章内容
   * 该方法会根据文章的发布者id和文章id生成文件路径，将文章内容以 Markdown 格式保存到该路径下。
   * 首先，会检查文件路径所在的目录是否存在，如果不存在，则会递归创建该目录。
   * 然后，将文章信息序列化为 JSON 格式，写入到文件中。
   */
  saveArticleContentDataByMarkdown(
    article: { id: number; publisher_id: number },
    markdown: string,
  ): void | never {
    const filePath = this.getArticleContentDataFilePath(
      article.publisher_id,
      article.id,
    );
    const markdownAbsolutePath = this.getArticleContentMarkdownFilePath(
      article.publisher_id,
      article.id,
    );
    const dirPath = path.dirname(filePath);
    if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: true });

    // 写出markdown
    fs.writeFileSync(markdownAbsolutePath, markdown, "utf-8");
    const dataInfo: ArticleContentData = {
      type: ArticleContentDataType.Markdown,
      version: ArticleContentDataVersion.V2,
    };
    // 写出 json 信息
    fs.writeFileSync(filePath, JSON.stringify(dataInfo), "utf-8");
  }

  saveArticleContentData(
    article: { id: number; publisher_id: number },
    contentType: "xml" | "XML" | "markdown" | "MARKDOWN",
    content: string,
  ) {
    // xml
    if (contentType === "xml" || contentType === "XML") {
      // 存储json
      this.saveArticleContentDataByXML(
        {
          publisher_id: article.publisher_id,
          id: article.id,
        },
        content,
      );
    }
    // markdown
    else if (contentType === "markdown" || contentType === "MARKDOWN") {
      // 存储 markdown
      this.saveArticleContentDataByMarkdown(
        {
          publisher_id: article.publisher_id,
          id: article.id,
        },
        content,
      );
    }
  }

  /**
   * 将文章内容元素转换为数组
   * @param contentElement 文章内容的DOM元素
   * @returns 返回一个对象数组，每个对象代表文章中的一个内容元素
   */
  articleContentXMLElement2Array(
    contentElement: Element,
  ): ArticleContentNode[] {
    // 将段落元素转换为数组
    const paragraphConvert2Array = function (
      paragraphElement,
    ): ArticleContentNode[] {
      const arr: ArticleContentNode[] = [];
      const 遍历子元素 = function (element) {
        // 遍历子元素，根据元素类型将其转换为相应的对象加入数组
        for (let index = 0; index < element.childNodes.length; index++) {
          const node = element.childNodes[index];

          // 同名元素
          // 若子元素与父元素同名，则递归处理子元素
          // <b><b>加粗</b></b>
          if (node.nodeName === element.nodeName) {
            // 解开
            遍历子元素(node);
            continue;
          }

          // 若子元素含有子元素，则将其转换为对应对象加入数组
          // <b><b>加粗</b></b>
          // <b><i>加粗倾斜</i></b>
          // <b><b>加粗</b><i>加粗倾斜</i></b>
          try {
            if (node.children.length && node.children.length > 0) {
              arr.push({
                type: node.nodeName.toLowerCase(),
                tagName: node.nodeName.toLowerCase(),
                // textContent: node.textContent,
                children: paragraphConvert2Array(node),
              });
              continue;
            }
          } catch (e) {
            console.error(e);
          }

          // 普通文本节点
          // 对文本节点、日期节点、时间节点进行处理
          // <b>加粗</b>
          if (
            node.nodeName === "#text" ||
            node.nodeName === "DATE" ||
            node.nodeName === "TIME"
          ) {
            arr.push({
              type: node.nodeName.toLowerCase(),
              tagName: node.nodeName.toLowerCase(),
              textContent: node.textContent,
            });
            continue;
          }

          // 普通情况
          // 默认情况下，将元素转换为对象加入数组
          arr.push({
            type: node.nodeName.toLowerCase(),
            tagName: node.nodeName.toLowerCase(),
            children: [
              {
                type: "#text",
                tagName: "#text",
                textContent: node.textContent,
              },
            ],
          });
        }
      };
      遍历子元素(paragraphElement);
      return arr;
    };

    // 将引用块元素转换为数组
    const blockquoteConvert2Array = function (
      blockquoteElement: Element,
    ): ArticleContentNode[] {
      const arr: ArticleContentNode[] = [];
      for (let i = 0; i < blockquoteElement.children.length; i++) {
        /** @type { Element } */
        const element = blockquoteElement.children[i];
        if (element.tagName === "P" || element.tagName === "PARAGRAPH") {
          arr.push({
            type: "paragraph",
            tagName: "p",
            textContent: element.textContent,
            children: paragraphConvert2Array(element),
          });
        } else if (element.tagName === "FOOTER") {
          arr.push({
            type: "footer",
            tagName: "footer",
            textContent: element.textContent,
          });
        }
      }
      return arr;
    };

    // 将列表元素转换为数组
    const listConvert2Array = function (
      listElement: Element,
    ): ArticleContentNode[] {
      const arr: ArticleContentNode[] = [];
      for (let index = 0; index < listElement.children.length; index++) {
        const element = listElement.children[index];
        // 递归处理列表项或嵌套列表
        if (element.tagName === "LIST-ITEM" || element.tagName === "ITEM") {
          arr.push({
            type: "li",
            tagName: "li",
            textContent: element.textContent,
            children: paragraphConvert2Array(element),
          });
        } else if (
          element.tagName === "LIST-UNORDERED" ||
          element.tagName === "UL"
        ) {
          arr.push({
            type: "ul",
            tagName: "ul",
            children: listConvert2Array(element),
          });
        } else if (
          element.tagName === "LIST-ORDERED" ||
          element.tagName === "OL"
        ) {
          arr.push({
            type: "ol",
            tagName: "ol",
            children: listConvert2Array(element),
          });
        }
      }
      return arr;
    };

    // 将图元素转换为数组
    const figureConvert2Array = function (
      figureElement: Element,
    ): ArticleContentNode[] {
      const arr: ArticleContentNode[] = [];
      for (let i = 0; i < figureElement.children.length; i++) {
        /** @type { Element } */
        const element = figureElement.children[i];
        if (element.tagName === "IMG" || element.tagName === "IMAGE") {
          arr.push({
            type: "img",
            tagName: "img",
            src: element.getAttribute("src"),
            alt: element.getAttribute("alt"),
          });
        } else if (element.tagName === "FIGCAPTION") {
          arr.push({
            type: "figcaption",
            tagName: "figcaption",
            textContent: element.textContent,
          });
        }
      }
      return arr;
    };

    // 将文章内容元素转换为数组
    const contentConvert2Array = function (
      contentElement: Element,
    ): ArticleContentNode[] {
      const arr: ArticleContentNode[] = [];
      for (let i = 0; i < contentElement.children.length; i++) {
        /** @type { Element } */
        const element = contentElement.children[i];

        // 根据元素类型将其转换为相应的对象加入数组
        if (element.tagName === "H2" || element.tagName === "SUBTITLE") {
          arr.push({
            type: "subtitle",
            tagName: "h2",
            textContent: element.textContent,
          });
        } else if (element.tagName === "P" || element.tagName === "PARAGRAPH") {
          arr.push({
            type: "paragraph",
            tagName: "p",
            textContent: element.textContent,
            children: paragraphConvert2Array(element),
          });
        } else if (element.tagName === "BLOCKQUOTE") {
          arr.push({
            type: "blockquote",
            tagName: "blockquote",
            textContent: element.textContent,
            children: blockquoteConvert2Array(element),
          });
        } else if (
          element.tagName === "LIST-UNORDERED" ||
          element.tagName === "UL"
        ) {
          arr.push({
            type: "ul",
            tagName: "ul",
            children: listConvert2Array(element),
          });
        } else if (
          element.tagName === "LIST-ORDERED" ||
          element.tagName === "OL"
        ) {
          arr.push({
            type: "ol",
            tagName: "ol",
            children: listConvert2Array(element),
          });
        } else if (element.tagName === "FIGURE") {
          arr.push({
            type: "figure",
            tagName: "figure",
            children: figureConvert2Array(element),
          });
        } else if (element.tagName === "IMG") {
          arr.push({
            type: "img",
            tagName: "img",
            src: element.getAttribute("src"),
            alt: element.getAttribute("alt"),
          });
        } else {
          arr.push({
            type: element.tagName.toLowerCase(),
            tagName: element.tagName.toLowerCase(),
            textContent: element.textContent,
          });
        }
      }

      return arr;
    };
    return contentConvert2Array(contentElement);
  }
  /**
   * 将XML格式的文章内容字符串转换为数组
   * @param content XML格式的文章内容字符串
   * @returns 返回一个对象数组，每个对象代表文章中的一个内容元素
   */
  articleContentXMLString2Array(content: string): ArticleContentNode[] {
    const xmlDOM = new JSDOM(`<body>${content}</body>`);
    const contentElement =
      xmlDOM.window.document.querySelector("content") ||
      xmlDOM.window.document.querySelector("main") ||
      xmlDOM.window.document.body;
    // console.log(contentElement);
    // 将找到的文章内容元素转换为数组
    return this.articleContentXMLElement2Array(contentElement);
  }

  articleContentNodeArray2HTMLv1(nodeArray: ArticleContentNode[]): string {
    if (!nodeArray) return null;

    const document = new JSDOM().window.document;
    const articleContentElement = document.createElement("div");
    for (let index = 0; index < nodeArray.length; index++) {
      const node = nodeArray[index];

      // 副标题
      if (node.type === "subtitle") {
        const h2 = document.createElement("h2");
        h2.textContent = typeof node.value === "string" ? node.value : "";
        articleContentElement.appendChild(h2);
      }
      // 段落
      else if (node.type === "paragraph") {
        const p = document.createElement("p");

        // 遍历并处理段落中的子节点
        const 遍历段落子节点 = function (
          parent: HTMLElement,
          childNode: ArticleContentNodeV1,
        ) {
          // 替换字符串中的尖括号
          if (typeof childNode.value === "string") {
            childNode.value = childNode.value
              .replaceAll("<", "&lt;")
              .replaceAll(">", "&gt;");
          }
          if (childNode.type === "text" || childNode.type === "#text") {
            parent.innerHTML += childNode.value;
            return;
          }

          // 老版本 和 date, time
          // 处理单个节点
          if (!Array.isArray(childNode.value)) {
            parent.innerHTML += `<${childNode.type}>${childNode.value}</${childNode.type}>`;
          }
          // 新版本, node.value 为 数组
          // 处理包含多个子节点的情况
          else {
            // 创建子节点，并遍历子节点内容，再加回父节点
            const childElement = document.createElement(childNode.type);
            for (let index = 0; index < childNode.value.length; index++) {
              const n = childNode.value[index];
              遍历段落子节点(childElement, n);
            }
            parent.appendChild(childElement);
          }
        };

        const childCount = Array.isArray(node.value) ? node.value.length : 0;
        for (let j = 0; j < childCount; j++) {
          const child = node.value[j];
          遍历段落子节点(p, child);
        }
        articleContentElement.appendChild(p);
      }
      // 图片
      else if (node.type === "figure") {
        const figure = document.createElement("figure");
        figure.className = "article-figure";

        const [img, figcaption] = Array.isArray(node.value) ? node.value : [];
        if (img) {
          const imgSrc = typeof img.value === "string" ? img.value : "";
          const imgElement = document.createElement("img");
          imgElement.decoding = "async";
          imgElement.loading = "lazy";
          imgElement.dataset.src = imgSrc;
          imgElement.src = imgSrc; // 占位图src
          imgElement.onload = function () {
            if (imgElement.src !== imgSrc) imgElement.src = imgSrc; // 实际图src
            imgElement.onload = null;
          };
          imgElement.onerror = function () {
            imgElement.src = imgErrorSrc; // 图片加载失败的备选图
          };
          figure.appendChild(imgElement);
        }
        if (figcaption) {
          const figcaptionElement = document.createElement("figcaption");
          figcaptionElement.textContent =
            typeof figcaption.value === "string" ? figcaption.value : "";
          figure.appendChild(figcaptionElement);
        }
        articleContentElement.appendChild(figure);
      }
      // 其他
      else {
        const valueString = typeof node.value === "string" ? node.value : "";
        const unknownElement = document.createElement(node.type);
        // 图片
        if (node.type === "img") {
          unknownElement.setAttribute("src", valueString);
          if (node.alt) unknownElement.setAttribute("alt", node.alt);
        }
        // 不是图片
        else {
          unknownElement.textContent = valueString;
        }
        articleContentElement.appendChild(unknownElement);
      }
    }
    return articleContentElement.innerHTML;
  }
  articleContentNodeArray2HTMLv2(nodeArray: ArticleContentNode[]): string {
    if (!nodeArray) return null;

    const document = new JSDOM().window.document;
    const articleContentElement = document.createElement("div");

    for (let index = 0; index < nodeArray.length; index++) {
      const node = nodeArray[index];

      // 段落
      if (node.type === "paragraph" || node.tagName === "p") {
        const p = document.createElement(node.tagName);

        // 遍历并处理段落中的子节点
        const 遍历段落子节点 = function (
          parent: HTMLElement,
          childNode: ArticleContentNode,
        ) {
          // 替换字符串中的尖括号
          if (childNode.textContent) {
            childNode.textContent = childNode.textContent
              .replaceAll("<", "&lt;")
              .replaceAll(">", "&gt;");
          }

          // 普通文本节点
          if (childNode.type === "text" || childNode.type === "#text") {
            parent.innerHTML += childNode.textContent;
            return; // 结束
          }

          // 子节点处理
          if (!childNode.children) return;
          // 处理单个节点
          if (childNode.children.length === 0) {
            parent.innerHTML += `<${childNode.tagName}>${childNode.textContent}</${childNode.tagName}>`;
          }
          // 处理包含多个子节点的情况
          else {
            // 创建子节点，并遍历子节点内容，再加回父节点
            const childElement = document.createElement(childNode.tagName);
            for (let index = 0; index < childNode.children.length; index++) {
              const n = childNode.children[index];
              遍历段落子节点(childElement, n);
            }
            parent.appendChild(childElement);
          }
        };

        for (let j = 0; j < (node.children ? node.children.length : 0); j++) {
          const child = node.children[j];
          遍历段落子节点(p, child);
        }
        articleContentElement.appendChild(p);
      }
      // 图片
      else if (node.type === "figure") {
        const figure = document.createElement(node.tagName);
        figure.className = "article-figure";

        const [img, figcaption] = node.children;
        if (img) {
          const imgElement = document.createElement("img");
          imgElement.decoding = "async";
          imgElement.loading = "lazy";
          imgElement.dataset.src = img.src;
          imgElement.src = img.src; // 占位图src
          imgElement.onload = function () {
            if (imgElement.src !== img.src) imgElement.src = img.src; // 实际图src
            imgElement.onload = null;
          };
          imgElement.onerror = function () {
            imgElement.src = imgErrorSrc; // 图片加载失败的备选图
          };
          figure.appendChild(imgElement);
        }
        if (figcaption) {
          const figcaptionElement = document.createElement("figcaption");
          figcaptionElement.textContent = figcaption.textContent;
          figure.appendChild(figcaptionElement);
        }
        articleContentElement.appendChild(figure);
      }
      // 其他
      else {
        const unknownElement = document.createElement(node.tagName);
        unknownElement.textContent = node.textContent;
        unknownElement.setAttribute("href", node.href);
        unknownElement.setAttribute("src", node.src);
        unknownElement.setAttribute("alt", node.alt);
        articleContentElement.appendChild(unknownElement);
      }
    }

    return articleContentElement.innerHTML;
  }
  /**
   * 根据 ArticleContentData，将其转换为HTML字符串。
   * @param data 包含文章内容和版本信息的对象。数据类型为ArticleContentData。
   * @returns 返回转换后的HTML字符串。
   */
  async articleContentData2HTML(data: ArticleContentData): Promise<string> {
    // 根据文章内容数据的版本，调用相应的转换方法
    // json
    const contentString = typeof data.content === "string" ? data.content : "";
    if (data.type === ArticleContentDataType.JSON) {
      const elements = Array.isArray(data.content) ? data.content : [];
      if (data.version === ArticleContentDataVersion.V1) {
        return this.articleContentNodeArray2HTMLv1(elements);
      } else if (data.version === ArticleContentDataVersion.V2) {
        return this.articleContentNodeArray2HTMLv2(elements);
      }
      return null;
    }
    // markdown
    else if (data.type === ArticleContentDataType.Markdown) {
      return await marked.parse(contentString);
    }
    // html
    else if (data.type === ArticleContentDataType.HTML) {
      return contentString;
    }
  }
  /**
   * 将 ArticleContentData 转换为Markdown格式的字符串。
   * @param jsonArray {any[]} - 待转换为Markdown格式的JSON数组。
   * @returns {string} - 转换后的Markdown格式字符串。
   */
  async articleContentData2Markdown(data: ArticleContentData): Promise<string> {
    if (data.type === ArticleContentDataType.Markdown) {
      return typeof data.content === "string" ? data.content : "";
    }
    const html = await this.articleContentData2HTML(data);
    return this.HTML2Markdown(html);
  }

  async getSortIDByName(sortName: string) {
    const result = await this.prisma.article_sort.findUnique({
      select: {
        id: true,
      },
      where: {
        name: sortName,
      },
    });
    // console.log(res);
    return result.id;
  }
  async searchSortIDByName(sortName: string) {
    const result = await this.prisma.article_sort.findMany({
      select: {
        id: true,
      },
      where: {
        name: {
          contains: sortName,
        },
      },
    });
    const arr = new Array(result.length);
    for (let index = 0; index < result.length; index++) {
      const element = result[index];
      arr[index] = element.id;
    }
    return arr;
  }
  async getSortNameByID(sortID: number | string) {
    if (typeof sortID !== "number") sortID = parseInt(sortID);
    if (isNaN(sortID)) {
      return undefined;
    }
    const result = await this.prisma.article_sort.findUnique({
      select: {
        name: true,
      },
      where: {
        id: sortID,
      },
    });
    // console.log(res);
    return result.name;
  }

  async getUserNicknameByID(uid: number | string) {
    if (typeof uid !== "number") uid = parseInt(uid);
    if (isNaN(uid)) {
      return undefined;
    }
    const result = await this.prisma.user.findUnique({
      select: {
        nickname: true,
      },
      where: {
        id: uid,
      },
    });
    // console.log(result);
    return result.nickname;
  }
  async getUserIDByNickname(nickname: string) {
    const result = await this.prisma.user.findUnique({
      select: {
        id: true,
      },
      where: {
        nickname: nickname,
      },
    });
    return result.id;
  }
  async getUserIDByUsername(username: string) {
    const result = await this.prisma.user.findUnique({
      select: {
        id: true,
      },
      where: {
        username: username,
      },
    });
    return result.id;
  }

  async dealArticleDTO(
    dto: ArticlePublishDTO | ArticleUpdateDTO,
  ): Promise<DealtArticleDTO> | never {
    if (!dto.sort && !dto.sort_id) {
      throw new ArticleDTODealError("sort is null");
    }

    // get sort id
    if (!dto.sort_id) {
      dto.sort_id = await this.getSortIDByName(dto.sort);
      if (!dto.sort_id) {
        throw new ArticleDTODealError("不存在的分类");
      }
    }

    if (!dto.cover) dto.cover = null;
    if (!dto.author) dto.author = null;
    if (!dto.author_home_page) dto.author_home_page = null;

    let contentHTML: string = "";
    // 判断 content_type
    if (dto.content_type === "xml" || dto.content_type === "XML") {
      // 验证 xml 格式
      const validResult = await validateXML(dto.content, articleContentXSDPath);
      // 错误
      if (!validResult.valid) {
        console.error(validResult.messages);
        throw new ArticleDTODealError("content格式错误");
      }
      contentHTML = dto.content;
    } else if (
      dto.content_type === "markdown" ||
      dto.content_type === "MARKDOWN"
    ) {
      const clean = await this.markdown2HTML(dto.content, true);
      dto.content = this.HTML2Markdown(clean);
      contentHTML = clean;
    } else {
      throw new ArticleDTODealError("content_type错误");
    }

    const xmlDOM = new JSDOM(`<body>${contentHTML}</body>`);
    const contentElement =
      xmlDOM.window.document.querySelector("content") ||
      xmlDOM.window.document.querySelector("main") ||
      xmlDOM.window.document.body;
    // console.log(contentElement);

    // 正则匹配 contentElement img src 作默认封面
    if (!dto.cover) {
      const match = /(?<=(img[^>]*src="))[^"]*/g.exec(contentElement.innerHTML);
      if (match && match[0]) {
        dto.cover = match[0];
      }
    }

    const dealtArticleDTO: DealtArticleDTO = {
      sort_id: dto.sort_id,
      sort_name: dto.sort,
      author: dto.author,
      author_home_page: dto.author_home_page,
      cover: dto.cover,
      title: dto.title,
      date: dto.date,
      content_type: dto.content_type,
      content: dto.content,
      textContent: contentElement.textContent,
    };
    return dealtArticleDTO;
  }

  async getArticleByID(
    id: number | string,
    option: {
      selectTextContent?: boolean;
      getRichContent?: boolean;
      contentType?: "default" | ArticleContentDataType;
    } = {
      selectTextContent: true,
      getRichContent: false,
      contentType: "default",
    },
  ) {
    if (!id) return null;
    if (typeof id !== "number") id = parseInt(id, 10);
    if (isNaN(id)) {
      return null;
    }
    if (typeof option.selectTextContent === "undefined") {
      option.selectTextContent = false;
    }
    if (typeof option.contentType === "undefined") {
      option.contentType = "default";
    }
    if (!option.getRichContent) {
      option.getRichContent = false;
    } else {
      option.selectTextContent = true;
    }

    let article: any;
    try {
      article = await this.prisma.article.findUnique({
        select: {
          id: true,
          sort_id: true,
          publisher_id: true,
          author: true,
          author_home_page: true,
          cover: true,
          title: true,
          date: true,
          content: option.selectTextContent,
          publish_time: true,
          update_time: true,
          sort: true,
          user: {
            select: {
              nickname: true,
            },
          },
        },
        where: {
          id: id,
        },
      });
      if (!article) {
        return null;
      }
    } catch (e) {
      console.error(e);
      return null;
    }

    // 发布者，作者
    article.publisher = article.user.nickname;
    if (!article.author) article.author = article.user.nickname;

    article.sort_id = article.sort.id;
    article.sort_name = article.sort.name;
    // string to date
    article.date = new Date(article.date);
    article.date = `${article.date.getFullYear()}/${article.date.getMonth() + 1}/${article.date.getDate()}`;

    // 修改 content
    if (option.selectTextContent) {
      article.content = article.content || "......";
    }
    if (option.getRichContent) {
      const contentData: ArticleContentData =
        this.getArticleContentData(article);
      if (!contentData) {
        // 没有本地文件存储，作html
        article.contentData = {
          type: ArticleContentDataType.HTML,
          content: article.content,
        };
        return article;
      }

      // 修改 ContentData 格式
      // default
      if (option.contentType === "default") {
        article.contentData = contentData;
      }
      // json
      else if (option.contentType === ArticleContentDataType.JSON) {
        if (contentData.type === ArticleContentDataType.JSON) {
          article.contentData = contentData;
        } else {
          const html = await this.articleContentData2HTML(contentData);
          const nodeArray = this.articleContentXMLString2Array(html);
          article.contentData = {
            type: ArticleContentDataType.JSON,
            version: ArticleContentDataVersion.V2,
            content: nodeArray,
          };
        }
      }
      // markdown
      else if (option.contentType === ArticleContentDataType.Markdown) {
        if (contentData.type === ArticleContentDataType.Markdown) {
          article.contentData = contentData;
        } else {
          const markdown = await this.articleContentData2Markdown(contentData);
          article.contentData = {
            type: ArticleContentDataType.Markdown,
            content: markdown,
          };
        }
      }
      // html
      else if (option.contentType === ArticleContentDataType.HTML) {
        if (contentData.type === ArticleContentDataType.HTML) {
          article.contentData = contentData;
        } else {
          const html = await this.articleContentData2HTML(contentData);
          const data: ArticleContentData = {
            type: ArticleContentDataType.HTML,
            content: html,
          };
          article.contentData = data;
        }
      }
    }

    return article;
  }
  /**
   * 获取 文章封面
   * @param id article id
   * @returns cover
   */
  async getArticleCoverByID(id: number | string) {
    try {
      if (typeof id !== "number") id = parseInt(id);
      if (isNaN(id)) {
        return null;
      }
      const article = await this.prisma.article.findUnique({
        select: {
          cover: true,
        },
        where: {
          id: id,
        },
      });
      if (article) {
        return article.cover;
      }
    } catch (e) {
      console.error(e);
      return null;
    }
    return null;
  }

  async getArticleTitleByID(id: number | string) {
    if (typeof id !== "number") id = parseInt(id);
    if (isNaN(id)) {
      return null;
    }
    try {
      const article = await this.prisma.article.findUnique({
        select: {
          title: true,
        },
        where: {
          id: id,
        },
      });
      if (article) {
        return article.title;
      }
    } catch (e) {
      console.error(e);
      return null;
    }
    return null;
  }

  async getArticlePublisherIDByID(id: number | string) {
    if (typeof id !== "number") id = parseInt(id);
    if (isNaN(id)) {
      return null;
    }
    try {
      const article = await this.prisma.article.findUnique({
        select: {
          publisher_id: true,
        },
        where: {
          id,
        },
      });
      if (article) {
        return article.publisher_id;
      }
    } catch (e) {
      console.error(e);
      return null;
    }
    return null;
  }

  /**
   * 根据提供的ID数组异步获取文章数组。
   * @param ids 可以是数字或字符串的数组，代表文章的ID。
   * @returns 返回一个文章对象的数组。如果输入的ID数组为空或无效，则返回null。
   */
  async getArticleArrayByIDs(
    ids: number[] | string[],
    option: {
      selectTextContent?: boolean;
      getRichContent?: boolean;
      contentType?: "default" | ArticleContentDataType;
    } = {
      selectTextContent: false,
      getRichContent: false,
      contentType: "default",
    },
  ) {
    // 检查IDs是否为空或未定义，如果是，则直接返回null
    if (!ids || ids.length === 0) return null;

    const array = [];
    // 遍历IDs数组，异步获取每个ID对应的文章，并将有效的文章添加到结果数组中
    for (let index = 0; index < ids.length; index++) {
      const id = ids[index];
      const article = await this.getArticleByID(id, option); // 异步获取文章
      if (article) array.push(article); // 如果成功获取文章，则将其添加到结果数组
    }
    return array;
  }

  /**
   * 分页获取 article
   * @param page 页数
   * @param pageSize 分页大小
   * @param selectTextContent 是否获取 textContent
   * @param getRichContent 是否获取 richContent
   * @param richContentLength 获取的 richContent 的长度
   * @returns article[]
   */
  async getArticleByPage(
    page: number,
    pageSize: number = 10,
    selectTextContent: boolean = true,
    getRichContent: boolean = false,
    richContentLength: number = undefined,
  ) {
    if (!page) return null;
    if (page <= 0) page = 1;
    if (getRichContent) {
      selectTextContent = true;
    }

    const arr: any[] = await this.prisma.article.findMany({
      select: {
        id: true,
        sort_id: true,
        publisher_id: true,
        author: true,
        author_home_page: true,
        cover: true,
        title: true,
        date: true,
        content: selectTextContent,
        publish_time: true,
        update_time: true,
        sort: true,
        user: {
          select: {
            nickname: true,
          },
        },
      },
      orderBy: {
        update_time: "desc",
      },
      take: pageSize,
      skip: (page - 1) * pageSize,
    });
    for (const article of arr) {
      // author
      if (!article.author) article.author = article.user.nickname;

      // sort
      article.sort_id = article.sort.id;
      article.sort_name = article.sort.name;

      // string to date
      article.date = new Date(article.date);
      article.date = `${article.date.getFullYear()}/${article.date.getMonth() + 1}/${article.date.getDate()}`;

      // 修改 content
      if (selectTextContent) {
        article.content = article.content || "......";
      }
      if (getRichContent) {
        article.contentData = this.getArticleContentData(
          article,
          richContentLength,
        );
      }
    }

    return arr;
  }
}
